package edu.psu.geovista.app.pcp;


import edu.psu.geovista.app.pcp.event.PcpSelectionEvent;
import edu.psu.geovista.app.pcp.light.LightPcp;
import edu.psu.geovista.app.pcp.plot.control.DfSetting;
import edu.psu.geovista.app.pcp.plot.control.Settings;
import edu.psu.geovista.app.pcp.plot.control.tools.AxisPlaceTool;
import edu.psu.geovista.app.pcp.plot.control.tools.BasicTool;
import edu.psu.geovista.app.pcp.plot.control.tools.DataSelectTool;
import edu.psu.geovista.app.pcp.plot.gui.BasicPcpUI;
import edu.psu.geovista.app.pcp.plot.services.ExternalService;
import edu.psu.geovista.app.pcp.toolkits.Math.JMath;
import edu.psu.geovista.app.pcp.toolkits.collection.CollectionUtils;
import edu.psu.geovista.app.plot.MultiYPlot;
import edu.psu.geovista.app.plot.axis.*;
import edu.psu.geovista.app.visualclassifier.classify.v2.ESVisualClassifier;
import edu.psu.geovista.classification.CategoryList;
import edu.psu.geovista.classification.ClassificationResultItf;
import edu.psu.geovista.common.awt.event.dataloading.*;
import edu.psu.geovista.data.model.AppDataModel;
import edu.psu.geovista.data.model.DefaultPlotModel;
import edu.psu.geovista.data.model.MetaData;
import edu.psu.geovista.data.shapefile.ShapeFileDataReader;
import edu.psu.geovista.io.csv.CSVFileDataReader;
import edu.psu.geovista.statistics.descriptive.Correlation;
import edu.psu.geovista.ui.event.*;
import epoviz.trex.common.swing.frame.AppComponent;

import javax.swing.*;
import javax.swing.event.EventListenerList;
import java.awt.*;
import java.io.Serializable;
import java.util.HashSet;


/*
* GeoVISTA Center (Penn State, Dept. of Geography)
* Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
* All Rights Researved.
* edu.psu.geovista.app.pcp.Pcp
* Description
*This library is developed and distributed for free usage,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* You can redistribute and/or modify it under the terms of the
* Simplified BSD License
*
*
*
*
*
*
*
* Date: May 29, 2003
* Time: 10:33:34 PM
* @author Jin Chen
*/


public class DfPcp extends MultiYPlot
        implements Pcp,SelectionListener,IndicationListener,DataSetListener,
        TSDataListener, FreezeListener,ColorArrayListener,
        ClassificationResultListener,Serializable, AppComponent {
    static final long serialVersionUID = -1759252593039724222L;



    public static final int defaultWidth=1000;  //NOT plot size, but the outline of Pcp
    public static final int defaultHeight=500;
    public static final int xaxisYOffset=20; // xaxis offset on vertical(Y) direction , relative to the bottom of YAxis

    static {//register PcpUI
        // UIManager.put(<UI Class ID>, <UI Class's full package name>");

        UIManager.put("PcpUI", "edu.psu.geovista.app.pcp.plot.gui.BasicPcpUI");
    }
    public static final boolean DEBUG = false;
    private boolean standAlone=false;//true if it is started as standalone version


    /************************  PlotImage ***********************/
    public static final int CSX=100; //coordinate start x
    public static final int CSY=47; //


    //data
    protected transient Object[] rawData;
    protected transient AppDataModel dataModel;
    protected Color[] datacolors;
    ClassificationResultItf classifyresult;
    //event
    protected EventListenerList listenerList = new EventListenerList();

    //components
    //protected transient CategoryListItf categoryList;
    protected boolean classifierNeeded=true;
    protected transient ESVisualClassifier vc;// =new JVisualClassifier();;don't do this, must update upone setDataModel since the variable list need to updated
    protected transient ExternalService externalService;
    //gui

    private int mode;//timeseries OR pcp
    private Dimension plotSize; //size of plot image
    private boolean legendShown=true;
    private Settings settings;
    //images
    final public static String RESOURCES="resources/";
    final public static String MODEL_ROOT= "edu/psu/geovista/app/pcp/";
    final public static String IMAGES=MODEL_ROOT+RESOURCES+"images/";


    // 
    public DfPcp() {
        //dataModel.getValues() ;

        this.setPreferredSize(new Dimension(defaultWidth,defaultHeight));
        this.setPlotSize (this.getPreferredSize() );// 


    }

    /*******************************************************************************************************
     *                setup
     *******************************************************************************************************/
    /**
     * init value only
     */
    protected void init() {
        Point offset=new Point(CSX,CSY);
        Point xaxisOffset=new Point(CSX,xaxisYOffset);
        this.setOffset(offset);
        this.setXaxisOffset(xaxisOffset);
    }
    /**
     * create X axis and Y axes. Set size
     */
    protected  void setup(){
        init();
        if (this.isValid(getDataModel())){
            this.setupXAxis() ;
            this.setupYAxes(((AppDataModel)this.dataModel));
            setBound();

        }

    }



    /**
     * set the plot size according to Y/X axes's activesize
     */
    public void setBound(){
        Dimension activeSize=this.getActiveSize() ;
        int w=activeSize.width +2*this.getOffset().x ;
        int h=activeSize.height +2*this.getOffset().y ; //??? use AXIS_ACTIVESIZE
        //this.setPreferredSize(new Dimension(w,h));
        this.setPlotSize(new Dimension(w,h));
        this.xaxis.setTickRange(new Range(w,0));


    }
    //setup xaxis and yxais
    protected  void setupAxes() {

        //Axis Tick range, this only for PCP, in which the tick range of Xaxis is (0,w)
        Dimension plotSize = this.getPlotSize();
        final int w = plotSize.width;
        this.xaxis.setTickRange(new Range(w,0));

    }

    protected  void setPlotSize(Dimension dimension) {
        this.plotSize =dimension;
    }
    /**
     *  create a axis for a dataModel's column with given index
     *   
     *  @param     displayIndex column's index in dataModel(rawDataIndex
     */
    protected  YAxis generateYAxis(int displayIndex, String axisLabel,edu.psu.geovista.data.model.AppDataModel dataModel){
        //1. label
        //String axisLabel=dataModel.getColumnName(rawIndex);
        int rawIndex=dataModel.getColumnIndexByName(axisLabel);
        //2.position:
        //Point position=AxisLayer.calculateAxisPosition(rawIndex) ;
        Point location=new Point();

        location.x = this.xaxis.getXByIndex((int)displayIndex) ; //initially, an axis's location is same as they are in dataModel
        location.y = this.getOffset().y;
        //System.out.println("location.x"+location.x);
        //YAxis axisy=new YAxis(axisLabel,index,AXIS_GAP,AXIS_ACTIVESIZE,position);

        //3. data range
        float dataMax=dataModel.getColumnMaxValue(rawIndex) ;
        float dataMin=dataModel.getColumnMinValue(rawIndex) ;
        Range dataRange=new Range(dataMax,dataMin);


        YAxis axisy=createYAxis(this.getDefaultYaxisSize() ,location,dataRange, axisLabel, rawIndex, null); //new YAxis
        setYAxisGUI(axisy);
     
        //ModelMetaData meta = dataModel.getMeta();
        MetaData meta = dataModel.getMetaData();
        String desc =null;
        if(meta!=null){
            desc=meta.getColumnDescription(axisLabel);
        }
        if(desc!=null)
            axisy.setDescription(desc);
        //axisy.getName().startsWith()
        // box plot
        float[] column=dataModel.getColumnValueAsFloat(rawIndex);
        BoxPlot boxPlot=new BoxPlot(column);
        axisy.setBoxplot(boxPlot);


        //axisy.setIndex(index);
        return axisy;

    }
    /**
     * only create a instance
     * @param activeSize
     * @param location
     * @param dataRange
     * @param axisLabel
     * @param rawIndex
     * @param categoryList
     * @return
     */
    protected YAxis createYAxis(int activeSize, Point location,Range dataRange,String axisLabel,int rawIndex, CategoryList categoryList){
        return new YAxis(activeSize ,location,dataRange, axisLabel, rawIndex, null);
    }
    private void setYAxisGUI(YAxis axis) {
        edu.psu.geovista.app.plot.ruler.Ruler ruler = axis.getRuler();
        ruler.setNumOfSection(1);
    }
    /**
     *
     * @param columnName
     * @return
     */
    public  YAxis generateYAxis(String columnName){
        AppDataModel dataModel=(AppDataModel) this.getDataModel();
        //int index=dataModel.getColumnIndexByName(columnName);
        int displayIndex=this.yaxisList.size();//attach the axis to the end
        if(displayIndex>=0){
            return generateYAxis(displayIndex,columnName,dataModel);
        }
        else{
            return null;
        }
    }
    /*******************************************************************************************************
     *                GUI
     *******************************************************************************************************/
    public Dimension getPlotSize(){
        return this.plotSize ;
    }



    protected void setupXAxis(){

        //if (this.xaxis ==null) { // 
        XAxisOrder xaxis = generateXAxis();
        //xaxis.setv
        xaxis.setName("XAxis");
        xaxis.setLabelFormat(JMath.FORMAT_INT);//label value is int type

        int activeSize=xaxis.getActiveSize() ;
        //To avoid any empty space on plot, make sure XAxis size is equal to Pcp size
        if(activeSize+2*CSX <this.getSize().width){
            int ps=this.getPlotSize().width;
            int deltaX=this.getSize().width-activeSize-CSX*2;//this.getPlotSize().width;
            xaxis.proposeChangeActiveSize(deltaX);  // 
        }
        this.xaxis=xaxis;
        setXaxisLocation();

    }
    protected XAxisOrder generateXAxis(){
        return new XAxisOrder(this.getNumOfAxis() );
    }

    /**
     * set XAxis location based on 1st YAXis's  height and locations
     */
    public  void setXaxisLocation() {
        int x = this.getXaxisOffset().x;
        int y = this.getOffset().y+this.getDefaultYaxisSize() +this.getXaxisOffset().y;
        this.xaxis.setAxisLocation(x, y);
    }

    protected void setupYAxes(AppDataModel dataModel ){
        this.yaxisList =new AxisList();
        int numOfAxis= dataModel.getColumnCount() ;

        for (int i=0;i<numOfAxis;i++){
            String axisLabel=dataModel.getColumnName(i);
            YAxis axisy=generateYAxis(i,axisLabel,dataModel );
            //dp{
            if (DEBUG){
                String n=axisy.getName() ;
                if(n.equalsIgnoreCase("RATE7175") )
                    System.out.println("axis name:"+n);
            }//dp}
            yaxisList.add(axisy);

        }
    }

    public int getDefaultYaxisSize() {
        YAxis axis=null;
        if (yaxisList!=null) {
            axis = this.yaxisList.getAxis(0);
        }
        if(axis!=null){
            return axis.getActiveSize() ;
        }
        else{
            return this.getPreferredSize().height-this.getOffset().y*2-this.getBarOffset();//barOffset;//defaultYaxisSize;
        }
    }
    protected int getNumOfAxis() {
        if(this.yaxisList ==null)
            return this.dataModel.getColumnCount() ;//.numOfAxis;
        else
            return yaxisList.size() ;
    }

    /**
     * set size, only positive size will be affect
     * @param w  if <0, ignore it
     * @param h  if <0, ignore it
     */
    public void setPositiveSize(int w, int h) {
        if (w<0&&h<0){
            return;
        }
        else if(w<0&& h>=0){
            int tmpw=this.getSize().width ;
            this.setPreferredSize(new Dimension(tmpw,h));
        }
        else if(w>=0&&h<0){
            int tmph=this.getSize().height ;
            this.setPreferredSize(new Dimension(w,tmph));

        }
        else if (w>=0&&h>=0) {
            this.setPreferredSize(new Dimension(w,h));
        }

    }
    /**
     * height= YAxis's activeSize
     * width=XAxis's activeSize
     * @return
     */
    public Dimension getActiveSize() {
        if  (activeSize==null)
            this.activeSize=new Dimension();
        // 
        // 
        AxisList axislist=this.getYaxisList();
        YAxis first=null;
        if (axislist!=null) {
            first = this.getYaxisList().getAxis(0);
        }
        if(first!=null){
            activeSize.height =this.getYaxisList().getAxis(0).getActiveSize() ;
            activeSize.width =this.xaxis.getActiveSize() ;
        }
        else{
            activeSize.height=300;
            activeSize.width =100;
        }
        return this.activeSize ;
    }
    public int getBarOffset(){
        return barOffset;
    }
    /*public Point getXAxisOffset(){
    return super.getXaxisOffset();
    }*/
    /*******************************************************************************************************
     *                self adjustment
     *******************************************************************************************************/

    /**
     * show only the specified axes in the plot
     *  
     * @param names
     */
    public void setValidYAxes(String [] names){
        BasicPcpUI ui = (BasicPcpUI) this.getUI();
        AxisPlaceTool placeAmd = (AxisPlaceTool) ui.getToolsMgr().getCmd(BasicTool.AxisPlaceT);
        boolean visible[]=new boolean[names.length ];
        for (int i=0;i<visible.length ;i++){
            visible[i]=true;
        }
        placeAmd.setYAxisAsValid(names,visible);
        this.fitwindow();
    }
    //auto fit the plot
    public void fitwindow(){
        BasicPcpUI ui = (BasicPcpUI) this.getUI();
        AxisPlaceTool apCmd= (AxisPlaceTool) ui.getToolsMgr().getCmd(BasicTool.AxisPlaceT);
        apCmd.fitwindow();
    }

    /********************************************************************
     *                set data
     ********************************************************************/
    /**
     *
     * @param rawData
     */
    public void setData(Object[] rawData) {
        //this.rawData =rawData;
        AppDataModel rawDataModel=DefaultPlotModel.createDefaultPlotModel(rawData);

        //setupVisualClassifier(rawData);
        this.setDataModel(rawDataModel);
        this.fireDataSetChanged(rawData);
        //After loading data, make plot fit in window
        // 
    }
    public void setDataModel(AppDataModel dataModel) {
        if (dataModel!=null){

            //setupVisualClassifier((DefaultPlotModel)dataModel);
            if(dataModel instanceof AppDataModel){
                //DefaultPlotModel md= (DefaultPlotModel) dataModel;
                //Hashtable timeSeriesValue=md.getColumnValues() ; // 
                /* if(timeSeriesValue!=null&&timeSeriesValue.size() >0){
                //This is time series data, need use time series mode
                this.setMode(DfPcp.MODE_TIMESERIES );
                }
                else{
                this.setMode(DfPcp.MODE_PCP );
                }*/
            }
            //clean and reset values
            this.dataModel = dataModel;
            this.yaxisList =null;//so the numberOfYAxis for XAxis will depend on model
            //this.categoryList =null;//Must, otherwise this.getCategoryList() still return value of previous session => colorBar appear abnormal


            this.setup();
            /* if(this.getVc() ==null){
            setupVisualClassifier((AppDataModel)dataModel);
            }*/
            //this.yaxisList.setCtgAxis(0,this.getCategoryList()) ; //todo esvc ctgCfg removed
            BasicPcpUI ui = (BasicPcpUI)this.getUI();
            try {
                ui.feedData(dataModel); //if(dataModel.getNumRecords()>100)
            } catch (Exception e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
           this.fitwindow(); 


        }
        //dataModel.getValue(1,1);
    }
    public AppDataModel getDataModel() {
        return dataModel;
    }
    private boolean isValid(AppDataModel dataModel){
        return true;
    }

    public Object[] getRawData() {
        return rawData;
    }
    /*******************************************************************************************************
     *                DfTimeSeries
     *******************************************************************************************************/
    //
    public void addTsListener(TSListener l) {
        this.listenerList.add(TSListener.class,l);
    }
    public void removeTsListener(TSListener l) {
        this.listenerList.remove(TSListener.class,l);
    }
    public void updateTimeSeries(String axisName) {
        Object[] listeners=this.listenerList.getListenerList() ;
        TSEvent e=null;
        for (int i=listeners.length -2;i>=0;i-=2){
            if (listeners[i]== TSListener.class){
                e=new TSEvent(this,axisName);
                ((TSListener)listeners[i+1]).tsChange(e);
            }


        }
    }
    /*******************************************************************************************************
     *                 UI delegate
     *******************************************************************************************************/
    //
    public String getUIClassID(){
        return "PcpUI";

    }

    public void updateUI(){
        try {
            // 
            final BasicPcpUI ui =  new BasicPcpUI((Pcp)this);//(BasicPcpUI)UIManager.getUI(this);
            setUI(ui);
            if (this.dataModel !=null) {
                //setUI((BasicPcpUI)UIManager.getUI(this));  jin removed 03082005
                try {
                    //       new BasicPcpUI((PcpPlot)this); //for debug UI exception
                } catch (Error e) {
                    e.printStackTrace();  //To change body of catch statement use Options | File Templates.
                }
            }
        }
        catch (ClassCastException ccex){
            ccex.printStackTrace() ;
        }
        catch(NullPointerException nullpe){
            nullpe.printStackTrace() ;
        }
        invalidate();

    }
    /*******************************************************************************************************
     *                services
     *******************************************************************************************************/
    /**
     * calculate correlation
     * @param ax1
     * @param ax2
     * @return
     */
    public  double getCorrelation(YAxis ax1,YAxis ax2){
        String n1 = ax1.getName();
        String n2 = ax2.getName();
        edu.psu.geovista.data.model.AppDataModel dataModel = (AppDataModel) this.getDataModel();
        int index1 = dataModel.getColumnIndexByName(n1);
        int index2 = dataModel.getColumnIndexByName(n2);
        double[] v1 = dataModel.getColumnValueAsDouble(index1);
        double[] v2 = dataModel.getColumnValueAsDouble(index2);
        double cr = Correlation.getCorrelationResult(v1,v2);
        if(Double.isNaN(cr)){
            return Double.NaN ;
        }
        return cr;
    }



    /*******************************************************************************************************
     *                Properties
     *******************************************************************************************************/

    public boolean isStandAlone() {
        return standAlone;
    }

    public void setStandAlone(boolean standAlone) {
        // this.standAlone = standAlone;
        ((BasicPcpUI)this.ui).setStandAlone(standAlone);

    }

    public int getMode() {
        return mode;
    }

    public void setMode(int mode) {
        this.mode = mode;
    }

    public  void setVisualClassifier(ESVisualClassifier vc){
        this.vc = vc;
        BasicPcpUI ui=(BasicPcpUI) this.getUI();

    }


    public void extractDataFromFile(String fileName) {

        try {
            Object[] rawData=null;
            if(fileName.endsWith(".shp") ){
                ShapeFileDataReader sfdr=new  ShapeFileDataReader();
                //"E:/jchen/MapBeanApp/data/westcoast.shp"
                sfdr.setFileName(fileName);
                //sfdr.setFileName("G:/work/samples/Parvis/data/jinSinglepupil.stf");//G:\work\samples\Parvis\data
                rawData=sfdr.getDataSet() ;
            }
            else if(fileName.endsWith("csv")){
                CSVFileDataReader csvR=new CSVFileDataReader();
                csvR.setFileName(fileName);
                rawData=csvR.getDataSet();
            }

            if (rawData!=null)
                this.setData(rawData);


        } catch (Exception e) {
            e.printStackTrace();  //To change body of catch statement use Options | File Templates.
        }

    }

    public  LightPcp createLightComponent(){
        return null;
    }
    /*******************************************************************************************************
     *                Events
     *******************************************************************************************************/
    /**
     * implements DataSetListener
     */
    public void addDataSetListener(DataSetListener l) {
        listenerList.add(DataSetListener.class, l);
    }

    /**
     * removes an DataSetListener from the button
     */
    public void removeDataSetListener(DataSetListener l) {
        listenerList.remove(DataSetListener.class, l);
    }
    protected void fireDataSetChanged(Object[] dataSet) {
        Object[] listeners=this.listenerList.getListenerList() ;
        DataSetEvent e=null;
        for (int i=listeners.length-2 ;i>=0;i-=2){
            if(listeners[i] ==DataSetListener.class ){
                e=new DataSetEvent(this,dataSet);
                ((DataSetListener)listeners[i + 1]).dataSetChanged(e);
            }


        }
    }
    /**
     * adds an SelectionListener
     */
    public void addSelectionListener (SelectionListener l) {
        listenerList.add(SelectionListener.class, l);




    }

    /**
     * removes an SelectionListener from the component
     */
    public void removeSelectionListener (SelectionListener l) {
        listenerList.remove(SelectionListener.class, l);


    }
    public void addFreezeListener (FreezeListener l) {
        listenerList.add(FreezeListener.class,l);

    }
   /* public void tryFireSelectionChanged (int[] newSelection, int mode) {
        //this.ses.send(newSelection);
        fireSelectionChanged (newSelection, mode);
    }*/
    public void fireColorArrayChanged(Color[] colors) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        ColorArrayEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ColorArrayListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e=new ColorArrayEvent(this,colors);
                }
                ((ColorArrayListener)listeners[i + 1]).colorArrayChanged(e);
            }
        }//next i
    }
    /**
     * Notify all listeners that have registered interest for
     * notification on this event type. The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @see javax.swing.event.EventListenerList
     */
    public  void fireSelectionChanged (PcpSelectionEvent evt) {
        //final CategoryItf ctg0 = vc.getCategories()[0];
        //final HashSet memberIds = ctg0.getMemberIds();
        //final int[] ids = CollectionUtils.convertToIntArray(memberIds);
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        PcpSelectionEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == SelectionListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new PcpSelectionEvent(this, evt.getSelection() );
                    e.setMode(evt.getMode() );
                    e.setModeIds(evt.getModeIds() );
                    e.setObSummaryVar(evt.getObSummaryVar() );
                }
                ((SelectionListener)listeners[i + 1]).selectionChanged(e);
            }
        }//next i

    }
    public  void fireSelectionChanged (int[] newSelection, int mode) {

        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        PcpSelectionEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == SelectionListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new PcpSelectionEvent(this, newSelection);
                    e.setMode(mode);
                }
                ((SelectionListener)listeners[i + 1]).selectionChanged(e);
            }
        }//next i

    }
   public  void fireFreezeChanged (FreezeEvent evt) {
       // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        FreezeEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == FreezeListener.class) {
                // Lazily create the event:
                e = new FreezeEvent(this, evt.getMode(),evt.getIds());
                FreezeListener freezeListener = ((FreezeListener) listeners[i + 1]);
                freezeListener.freezeChanged(e);
            }
        }//next i
   }
   

    public void selectionChanged(SelectionEvent e) {

        ((BasicPcpUI)ui).showSelection(e);

    }

    public void freezeChanged(FreezeEvent e) {
        ((BasicPcpUI)ui).freezeChanged(e);
    }
    /**
     * adds an IndicationListener
     */
    public void addIndicationListener(IndicationListener l) {
        listenerList.add(IndicationListener.class, l);

    }

    /**
     * removes an IndicationListener from the component
     */
    public void removeIndicationListener(IndicationListener l) {
        listenerList.remove(IndicationListener.class, l);
    }
    /**
     * Notify all listeners that have registered interest for
     * notification on this event type. The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @see javax.swing.event.EventListenerList
     */
    public  void fireIndicationChanged (int indicator) {

        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        IndicationEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == IndicationListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new IndicationEvent(this, indicator);
                }
                ((IndicationListener)listeners[i + 1]).indicationChanged(e);
            }
        }//next i

    }
    public void indicationChanged(IndicationEvent e) {

        ((BasicPcpUI)ui).indicationChanged(e);
    }

    public void dataSetChanged(DataSetEvent e) {
        try {
            Object[] data=e.getDataSet() ;
            if(e instanceof AppDataEvent){
                AppDataEvent ae=(AppDataEvent) e;
                AppDataModel dataModel=ae.getDataModel() ;
                if(dataModel!=null){
                    this.setDataModel(dataModel);
                    return;
                }
            }

            this.setData(data);
        } catch (Exception e1) {
            if(DEBUG)
                e1.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            //throw e1;
        }

    }
    public void tsDataChanged(TSDataEvent e) {
        edu.psu.geovista.data.model.AppDataModel model=e.getDataModel() ;
        this.setDataModel(model);
    }


    /**
     * implements ColorArrayListener
     */
    public void addColorArrayListener(ColorArrayListener l) {
        /*JVisualClassifier vc=this.getVc();
        if(vc!=null){
        vc.addColorArrayListener(l);
        }*/
        this.listenerList.add(ColorArrayListener.class,l);

        //it gets an event
    }

    /**
     * removes an ColorArrayListener from the component
     */
    public void removeColorArrayListener(ColorArrayListener l) {

        this.listenerList.remove(ColorArrayListener.class,l);

    }
    /*******************************************************************************************************
     *                properties
     *******************************************************************************************************/
    public Settings getSettings() {
        if( settings==null){
            //need put here since the method is called by UI even before execute constructor
            settings = createSettings();
        }
        return settings ;
    }
    protected Settings createSettings(){
        Settings settings=new DfSetting();
        return settings;
    }

    public ExternalService getExternalService() {
        return externalService;
    }

    public void setExternalService(ExternalService externalService) {
        this.externalService = externalService;
    }
    public boolean isClassifierNeeded() {
        return classifierNeeded;
    }

    public void setClassifierNeeded(boolean classifierNeeded) {
        this.classifierNeeded = classifierNeeded;
    }

    public Color[] getDatacolors() {
        if(datacolors==null){
            datacolors=new Color[0];
        }
        return datacolors;
    }

    public void setDatacolors(Color[] datacolors) {


        this.datacolors = datacolors;
    }

    public ClassificationResultItf getClassifyresult() {
        return classifyresult;
    }

    public void setClassifyresult(ClassificationResultItf classifyresult) {
        this.classifyresult = classifyresult;
    }
    /*******************************************************************************************************
     *                Test
     *******************************************************************************************************/
    /**
     * simulate  selecting action, it will fire selection event. Only for testing purpose
     * @param ids
     */
    public void selectData(int[] ids){
        BasicPcpUI ui = (BasicPcpUI) this.getUI();
        final DataSelectTool dst = (DataSelectTool) ui.getToolsMgr().getCmd(BasicTool.DataSelectT );
        HashSet idSet=CollectionUtils.convertArrayToSet(ids);
        dst.setLastSelection(idSet);
        dst.selectData(idSet,true,true,true);

    }

    /**
     *  load a data file, usually for testing purpose
     * @param file
     */
    public void loadData(String  file) {
       // ShapeFileDataReader sfdr=new  ShapeFileDataReader();
        CSVFileDataReader sfdr=new CSVFileDataReader();
        //"E:/jchen/MapBeanApp/data/westcoast.shp"
        sfdr.setFileName(file);//"G:/work/Test/parvis/data/GeoMap/westcoast.shp");
        //sfdr.setFileName("G:/work/samples/Parvis/data/jinSinglepupil.stf");//G:\work\samples\Parvis\data
        Object[] rawData=sfdr.getDataSet() ;
        //extractDataFromFile(file);
        setData(rawData)  ;
    }


    public void colorArrayChanged(ColorArrayEvent e) {
        //
        Color[] colors=e.getColors() ;
        this.setDatacolors(colors);
        ((BasicPcpUI)this.ui).colorArrayChanged(e);
    }

    public void classificationResultChanged(ClassificationResultEvent e) {


        ClassificationResultItf cr = (ClassificationResultItf) e.getClassificationResult();
        this.setClassifyresult(cr);
        DataSelectTool dst = (DataSelectTool) ((BasicPcpUI)this.ui).getToolsMgr().getCmd(BasicTool.DataSelectT);
        if(dst!=null)
        dst.processClassification(cr);


    }

    public boolean isLegendShown() {
        return legendShown;
    }

    public void setLegendShown(boolean legendShown) {
        this.legendShown = legendShown;
    }



}

